#include "util.hpp"

#define private public
#include "mesh.hpp"
#include "mesh_testFunctions.hpp"
#undef private

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/catch_approx.hpp>
#include <utility>

using namespace hms;

using Catch::Approx;
using Catch::Matchers::ContainsSubstring;

TEST_CASE( "Creating mesh from edges, testing info" )
{
	SECTION( "rectangular mesh" )
	{
		/*                       cells + ghost cells
		 *                           ___________
		 *       edges              |     |     |
		 *                          | 10  | 11  |
		 *    _10____11__      _____|_____|_____|_____
		 *   |     |     |    |     |     |     |     |
		 *  1|    3|    5|    |  9  |  1  |  3  |  5  |
		 *   |_____|_____|    |_____|_____|_____|_____|
		 *   |  8  |  9  |    |     |     |     |     |
		 *  0|    2|    4|    |  8  |  0  |  2  |  4  |
		 *   |_____|_____|    |_____|_____|_____|_____|
		 *      6     7             |     |     |
		 *                          |  7  |  6  |
		 *                          |_____|_____|
		 *
		 */
		auto mesh { testMeshRectangles(true) };
		
		REQUIRE( mesh.nVertices() == 9 );
		REQUIRE( mesh.nEdges() == 12 );
		REQUIRE( mesh.nDomainCells() == 4 );
		REQUIRE( mesh.nGhostCells() == 8 );
		REQUIRE( mesh.nCells() == 12 );
		REQUIRE( mesh.domainArea() == Approx(4) );
	}
	SECTION( "triangular mesh" )
	{
		auto mesh { testMeshTriangles(true) };
		
		REQUIRE( mesh.nVertices() == 6 );
		REQUIRE( mesh.nEdges() == 9 );
		REQUIRE( mesh.nDomainCells() == 4 );
		REQUIRE( mesh.nGhostCells() == 6 );
		REQUIRE( mesh.nCells() == 10 );
		REQUIRE( mesh.domainArea() == Approx(14) );
	}
}

TEST_CASE( "Adding boundary patch to mesh" )
{
	auto mesh { testMeshRectangles(true) };
	
	/* adding them in an unsorted fashion to check whether they get sorted */
	mesh.addBoundaryPatch( "right",  Array2i{ 4, 5} );
	mesh.addBoundaryPatch( "top",    Array2i{10,11} );
	mesh.addBoundaryPatch( "left",   Array2i{ 8, 9} );
	mesh.addBoundaryPatch( "bottom", Array2i{ 6, 7} );
	REQUIRE_THROWS_WITH( mesh.addBoundaryPatch("inner", Array2i{0,1}),
		ContainsSubstring("not a boundary edge")
	);
	
	auto req = [&](std::string&& name, ArrayXi&& edges){
		REQUIRE( exactlyEqual( mesh.boundaryPatch(name).edges(), edges ) );
	};
	req("bottom", Array2i{6,7});
	req("left"  , Array2i{8,9});
	req("right" , Array2i{4,5});
	req("top"   , Array2i{10,11});

	/* they must be sorted correctly */
	REQUIRE( mesh.boundaryPatches()[0].name() == "bottom" );
	REQUIRE( mesh.boundaryPatches()[1].name() == "left"   );
	REQUIRE( mesh.boundaryPatches()[2].name() == "right"  );
	REQUIRE( mesh.boundaryPatches()[3].name() == "top"    );
}

TEST_CASE( "Mesh: Iterating over cells" ){
	std::vector<Index>
		all, domain, ghost,
		allTest1, allTest2,
		domainTest1, domainTest2,
		ghostTest1, ghostTest2;
	
	auto test = [&](const Mesh& mesh){
		auto pushIndex = [&]( std::vector<Index>& vec, Index i ){
			OMP_PRAGMA( critical (push_back) )
			vec.push_back(i);
		};
		mesh.forEachCell( [&](const auto& cell){
			pushIndex( allTest1, cell.index() );
		});
		mesh.forEachCellIndex( [&](Index i){
			pushIndex( allTest2, i );
		});
		mesh.forEachDomainCell( [&](const auto& cell){
			pushIndex( domainTest1, cell.index() );
		});
		mesh.forEachDomainCellIndex( [&](Index i){
			pushIndex( domainTest2, i );
		});
		mesh.forEachGhostCell( [&](const auto& cell){
			pushIndex( ghostTest1, cell.index() );
		});
		mesh.forEachGhostCellIndex( [&](Index i){
			pushIndex( ghostTest2, i );
		});
		std::sort( allTest1.begin(), allTest1.end() );
		std::sort( allTest2.begin(), allTest2.end() );
		std::sort( domainTest1.begin(), domainTest1.end() );
		std::sort( domainTest2.begin(), domainTest2.end() );
		std::sort( ghostTest1.begin(), ghostTest1.end() );
		std::sort( ghostTest2.begin(), ghostTest2.end() );
		REQUIRE( all == allTest1 );
		REQUIRE( all == allTest2 );
		REQUIRE( domain == domainTest1 );
		REQUIRE( domain == domainTest2 );
		REQUIRE( ghost == ghostTest1 );
		REQUIRE( ghost == ghostTest2 );
	};

	SECTION( "Rectangular mesh" ){
		auto mesh { testMeshRectangles(true) };
		all = {
			0, 1, 2, 3,
			4, 5, 6, 7, 8, 9, 10, 11
		};
		domain = { 0, 1, 2, 3 };
		ghost = { 4, 5, 6, 7, 8, 9, 10, 11 };

		test(mesh);
	}
	SECTION( "Triangular mesh" ){
		auto mesh { testMeshTriangles(true) };
		all = {
			0, 1, 2, 3,
			4, 5, 6, 7, 8, 9
		};
		domain = { 0, 1, 2, 3 };
		ghost = { 4, 5, 6, 7, 8, 9, };

		test(mesh);
	}
	SECTION( "Mixed mesh" ){
		auto mesh { testMeshMixed(true) };
		all = {
			0, 1,
			2, 3, 4, 5, 6
		};
		domain = { 0, 1 };
		ghost = { 2, 3, 4, 5, 6 };
	}
}